package com.mscsoft.midi;

import java.io.*;
import java.util.*;
import javax.sound.midi.*;

import com.mscsoft.musicscore.note.*;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

public class MidiReader {
    private static final Logger logger = LogManager.getLogger(MidiReader.class.getSimpleName());

    public static final int NOTE_ON = 0x90;
    public static final int NOTE_OFF = 0x80;
    protected static final String[] NOTE_NAMES = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" };

    private File file;// 读取的midi文件
    private int column;
    private int tpqn = -1;// 每个四分音符中MIDI Tick的数量
    private int qn = -1;// 四分音符的时长(微秒)
    private double bpm = -1;// 每分钟节拍数

    private List<Note> notes = new ArrayList<>();

    public double getBpm() {
        return bpm;
    }

    public List<Note> getNotes() {
        return notes;
    }

    public MidiReader(String pathname, int column) {
        file = new File(pathname);
        this.column = column;
    }

    public void read() throws InvalidMidiDataException, IOException {
        readTPQN();
        readTracks();
        logger.info("Successfully Read Midi: TPQN={}, BPM={}, QN={}", tpqn, bpm, qn);
    }

    /** 读每个四分音符中MIDI Tick的数量 */
    private void readTPQN() throws IOException {
        try (FileInputStream fi = new FileInputStream(file)) {
            int[] head = new int[14];
            for (int i = 0; i < head.length; i++) {
                head[i] = fi.read();
            }
            tpqn = (head[12] << 8) + head[13];
        }
    }

    /** 依次读取midi中的所有轨道 */
    private void readTracks() throws InvalidMidiDataException, IOException {
        Sequence sequence = MidiSystem.getSequence(file);
        Track[] tracks = sequence.getTracks();
        for (int i = 0; i < tracks.length; i++) {
            Track track = tracks[i];
            logger.info("Track {}: size = {}", i, track.size());
            readTrack(track);
        }
        notes.sort((n1, n2) -> (int) ((n1.tick - n2.tick) % Integer.MAX_VALUE));
    }

    /** 读取midi中的单个轨道信息 */
    private void readTrack(Track track) {
        for (int j = 0; j < track.size(); j++) {
            MidiEvent event = track.get(j);
            readEvent(event);
        }
    }

    private TreeMap<Integer, Long> holdMap = new TreeMap<>();

    /** 读取轨道上的单个Midi事件 */
    private void readEvent(MidiEvent event) {
        long tick = event.getTick();
        MidiMessage message = event.getMessage();
        if (message instanceof ShortMessage) {// 音符信息
            ShortMessage sm = (ShortMessage) message;
            int command = sm.getCommand();
            int channel = sm.getChannel();
            int key = sm.getData1();
            int octave = (key / 12) - 1;
            int note = key % 12;
            String noteName = NOTE_NAMES[note];
            int velocity = sm.getData2();
            if (command == NOTE_ON) {
                if (channel == 0) {// 点按音符
                    notes.add(new NoteTap(tick * qn / tpqn, (column - 1 - note) % column));
                } else if (channel == 1) {// 长条音符
                    holdMap.put(note, tick);// 暂存起始tick
                }
                logger.debug("@{} Channel:{}, Note on, {}{} key={} velocity={}", tick, channel, noteName,
                        octave, key, velocity);
            } else if (command == NOTE_OFF) {
                if (channel == 1) {
                    long startTick = holdMap.get(note);// 取出起始tick
                    long duration = (tick - startTick) * qn / tpqn;// 音符长度
                    notes.add(new NoteHold(startTick * qn / tpqn, duration, (column - 1 - note) % column));
                    holdMap.remove(note);
                }
                logger.debug("@{} Channel:{}, Note off, {}{} key={} velocity={}", tick, channel, noteName,
                        octave, key, velocity);
            }
        } else {
            processOtherMessage(message);
        }
    }

    /** 处理非音符信息的其他信息 */
    private void processOtherMessage(MidiMessage message) {
        MetaMessage mm = (MetaMessage) message;
        byte[] m = mm.getMessage();
        if ((m[0] & 0xFF) == 0xFF && (m[1] & 0xFF) == 0x51 && (m[2] & 0xFF) == 0x03) {
            qn = ((m[3] & 0xFF) << 16) + ((m[4] & 0xFF) << 8) + (m[5] & 0xFF);
            bpm = 60000000.0 / qn;
        }
    }
}